/*
    3D Office Environment
    Copyright (C) 2011  Till Merker

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

#include <Plugins/BSPSceneManager/OgreBspSceneManagerPlugin.h>

#include "BSPOfficeScene.h"

namespace OfficeEnv {

SceneManager* BSPOfficeScene::createSceneManager() {
    /* register + create BSP scene manager */
    mRoot->installPlugin(new BspSceneManagerPlugin());
    return mRoot->createSceneManager("BspSceneManager");
}

void BSPOfficeScene::loadScene(String sceneFile) {
    
    mSceneMgr->setWorldGeometry(sceneFile);   
    
    /* Parse entities to get position + orientation of desktops */
    Quake3Level q3;
    DataStreamPtr stream = ResourceGroupManager::getSingleton().openResource(sceneFile, ResourceGroupManager::getSingleton().getWorldResourceGroupName());
    q3.loadFromStream(stream);    
    char *strEnt = (char*)q3.mEntities;
    
    Vector3 origin(0,0,0);
    Degree angle;
    bool isDesktop = false;
    int desktop = -1;
    
    char *lineend = strchr(strEnt, '\n');
    while (lineend != 0) {
	*lineend = '\0';
	String line = strEnt;
	
	strEnt = lineend + 1;
	StringUtil::trim(line);
	if (line.length() > 0) {
	    StringUtil::toLowerCase(line);
	    std::size_t pos;
	    while( ( pos = line.find("\"",0) ) != String::npos )
		line = line.replace(pos, 1, "");
	    StringVector vecparams = StringUtil::split(line);
	    StringVector::iterator params = vecparams.begin();

	    if (params[0] == "origin") {
		origin.x = atof(params[1].c_str());
		origin.y = atof(params[2].c_str());
		origin.z = atof(params[3].c_str());
	    }
	    if (params[0] == "angle") {
		angle = Degree(atof(params[1].c_str()));
	    }
	    if (params[0] == "desktop") {
		desktop = atoi(params[1].c_str());
	    }	    
	    if (params[0] == "classname") {
		if (params[1] == "info_desktop") isDesktop = true;
	    }
	    
	    if (params[0] == "}") {
		if (isDesktop && desktop < MAX_DESKTOPS && desktop >= 0) {
		    mDesktopPosition[desktop] = origin;
		    mDesktopOrientation[desktop].FromAngleAxis(angle + Degree(90), Vector3::UNIT_Z);	    
		}
		isDesktop = false;
	    }
	}

	lineend = strchr(strEnt, '\n');
    }
    
    /* Scene Query for collision detecion */
    mRaySceneQuery = mSceneMgr->createRayQuery(Ray());
    mRaySceneQuery->setSortByDistance(true, 1);    
    
    /* Get spawn point from map and glue to ground */
    ViewPoint vp = mSceneMgr->getSuggestedViewpoint();
    glueToGround(vp.position);
    
    /* Initialize camera */
    mCamera = mSceneMgr->createCamera("Camera");            
    mCamera->setAspectRatio((float) mWidth / mHeight);    
    mCamera->setNearClipDistance(5);
    
    mCamera->setPosition(vp.position);
    mCamera->setOrientation(vp.orientation);
    mCamera->pitch(Degree(90));
    mCamera->setFixedYawAxis(true, mUp);
    mCamera->yaw(Degree(-90));
        
}

void BSPOfficeScene::getDesktopEntity(int desktopIndex, SceneNode **node, Entity **entity) {
    char entName[16];
    char matName[16];
    snprintf((char*)&entName, 16, "DeskEnt%i", desktopIndex);
    snprintf((char*)&matName, 16, "DeskMat%i", desktopIndex);
    /* Creat desktop entity */
    (*entity) = mSceneMgr->createEntity(entName, "DeskMesh");
    (*entity)->setMaterialName(matName);
    
    /* Create scene node */
    (*node) = mSceneMgr->getRootSceneNode()->createChildSceneNode();
    (*node)->attachObject(mDesktopEnt[desktopIndex]);
	    
    /* Set position + orientation */
    (*node)->setPosition(mDesktopPosition[desktopIndex]);
    (*node)->setOrientation(mDesktopOrientation[desktopIndex]);
    (*node)->pitch(Radian(Math::PI / 2));
}

Vector3 BSPOfficeScene::getUpVector() {
    return Vector3::UNIT_Z;
}

void BSPOfficeScene::getPitchLimits(Radian &minPitch, Radian &maxPitch) {    
    minPitch = Degree(0);
    maxPitch = Degree(180);  
}

void BSPOfficeScene::moveTo(Vector3 dest) {
    const Vector3 &start = mCamera->getPosition();
    const Vector3 delta(dest - start);
    
    /* Check if we're moving into an obstacle */
    mRaySceneQuery->setWorldFragmentType(SceneQuery::WFT_PLANE_BOUNDED_REGION);     
    mRaySceneQuery->setRay(Ray(start - mUp * 48, delta));
    RaySceneQueryResult &result = mRaySceneQuery->execute();
    for (RaySceneQueryResult::iterator itr = result.begin(); itr != result.end(); itr++) {
	if(itr->worldFragment) {
	    if(itr->distance < 8) {
		/* Yes, abort */
		return;
	    }
	}
    }
    
    glueToGround(dest);    
    mCamera->setPosition(dest);    
}

void BSPOfficeScene::glueToGround(Vector3 &pos) {
    /* Shoot a ray downwards to find ground */
    mRaySceneQuery->setWorldFragmentType(SceneQuery::WFT_SINGLE_INTERSECTION); 
    mRaySceneQuery->setRay(Ray(pos - mUp * 48, -mUp));
    RaySceneQueryResult &result = mRaySceneQuery->execute();
    for (RaySceneQueryResult::iterator itr = result.begin(); itr != result.end(); itr++) {
	if(itr->worldFragment) {
	    /* Set position to 64 units above ground */
	    pos = itr->worldFragment->singleIntersection;
	    pos.z = round(pos.z);
	    pos = pos + mUp * 64;
	    break;
	}
    }
}

}
